package com.heima.media.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.heima.article.dto.ArticleDto;
import com.heima.article.feign.ArticleFeign;
import com.heima.common.aliyun.service.AliyunGreenService;
import com.heima.common.dto.ResponseResult;
import com.heima.common.enums.AppHttpCodeEnum;
import com.heima.common.enums.MediaEnum;
import com.heima.common.minio.service.MinIOService;
import com.heima.common.util.SensitiveWordUtil;
import com.heima.media.dto.ContentDto;
import com.heima.media.entity.WmChannel;
import com.heima.media.entity.WmNews;
import com.heima.media.entity.WmSensitive;
import com.heima.media.entity.WmUser;
import com.heima.media.service.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: itheima
 * @create: 2022-03-26 11:10
 */
@Service
public class AuditServiceImpl implements IAuditService {

    @Autowired
    @Lazy //解决对象循环依赖问题
    private IWmNewsService wmNewsService;

    @Autowired
    private ArticleFeign articleFeign;

    @Autowired
    private IWmUserService wmUserService;

    @Autowired
    private IWmChannelService wmChannelService;

    @Autowired
    private MinIOService minIOService;

    @Autowired
    private AliyunGreenService aliyunGreenService;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 根据传入自媒体文章ID 审核文章内容
     *
     * @param id 自媒体文章ID
     */
    //@Override
    //public void audit(Integer id) {
    //    //1.根据自媒体文章ID 查询自媒体文章
    //    WmNews wmNews = wmNewsService.getById(id);
    //    //2.调用自媒体文章审核方法
    //    this.audit(wmNews);
    //}

    /**
     * 审核自媒体文章合法性(文本，图片)
     * 情况一：文章可能已经通过了审核，但是没有到发布时间，修改审核状态 。 到了发布时间，直接远程调用文章微服务发表文章即可
     *
     * @param wmNews 自媒体文章ID
     */
    @Async("leadnewsExecutor")
    @Override
    public void audit(WmNews wmNews) {
        System.err.println("线程信息" + Thread.currentThread().getId() + Thread.currentThread().getName());
        //1.得到 自动审核通过 或者 人工审核通过自媒体文章状态
        Integer status = wmNews.getStatus();
        if (status.equals(MediaEnum.NEWS_SUBMIT_STATUS_ARTIFICIAL_PASS.getVal()) || status.equals(MediaEnum.NEWS_SUBMIT_STATUS_PASS.getVal())) {
            //1.1 根据文章发布时间是否大于当前系统时间--决定是否要发布（正式）文章
            if (wmNews.getPublishTime() == null || System.currentTimeMillis() >= wmNews.getPublishTime().getTime()) {
                //1.2 远程调用文章微服务发布文章
                remoteSaveArticle(wmNews);
            }
            return;
        }

        //2.处理自媒体文章状态为待审核，则进入自动审核业务流程中  检测文本，以及图片是否合法
        if (status.equals(MediaEnum.NEWS_SUBMIT_STATUS_COMMIT.getVal())) {


            //2.0 获取正文中的图片 文本
            Map<String, Object> map = getTextAndImageFromContent(wmNews.getContent());

            //2.1 得到审核的图片（文章封面，正文中图片）
            String coverImages = wmNews.getImages();
            //2.3 调用阿里云内容安全进行审核  违规：修改审核状态，结束方法  通过：继续看是否到了发布时间
            if (StringUtils.isNotBlank(coverImages)) {
                String[] split = coverImages.split(",");
                //TODO Arrays.asList创建集合非java.util包下ArrayList
                List<String> images = Arrays.asList(split);
                List<String> contentImages = (List<String>) map.get("images");
                if (!CollectionUtils.isEmpty(images)) {
                    contentImages.addAll(images);
                }
                boolean result = checkImage(images, wmNews);
                if (!result) {
                    return;
                }
            }

            //2.2 得到审核文本（文章标题，文章正文中文本）
            String text = map.get("text") + wmNews.getTitle();

            //TODO 增加自己敏感词判断
            boolean flag = checkSensitive(text, wmNews);
            if (!flag) {
                return;
            }

            boolean result = checkText(text, wmNews);
            if (!result) {
                return;
            }
            //2.4审核通过后（两种情况：1未到发布时间（修改状态为8）  2 已经到了发布时间-远程发布正式文章）
            if (wmNews.getPublishTime() == null || wmNews.getPublishTime().getTime() <= System.currentTimeMillis()) {
                //2.4.1 未指定发布时间  或者  发布时间小于当前系统时间------》则直接进行发布
                remoteSaveArticle(wmNews);
            } else {
                //2.4.2 自媒体文章内容审核通过，但是没有到发布时间 ---》 TODO 采用redis延迟队列解决:采用Redisson框架来实现
                wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_PASS.getVal());
                //2.4.3 计算延迟消息时间，发送延迟消息
                long delayTime = wmNews.getPublishTime().getTime() - System.currentTimeMillis();
                RBlockingDeque<String> blockingDeque = redissonClient.getBlockingDeque("article-publish");
                RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
                delayedQueue.offer(wmNews.getId().toString(), delayTime, TimeUnit.MILLISECONDS);
                wmNewsService.updateById(wmNews);
            }
        }
    }


    /**
     * 远程调用文章微服务保存文章。更新自媒体文章表中逻辑外键：文章ID article_id
     *
     * @param wmNews 审核通过 自媒体文章
     */
    private void remoteSaveArticle(WmNews wmNews) {
        //1.构建保存正式发布文章对象ArticleDTO 文章信息，文章内容
        ArticleDto articleDto = new ArticleDto();
        //1.1 文章基本信息可以从自媒体文章对象中拷贝得到，封面，标签等属性
        BeanUtils.copyProperties(wmNews, articleDto);
        //1.2 设置文章作者信息
        //1.2.1 根据自媒体用户ID查询自媒体用户对象
        Integer userId = wmNews.getUserId();
        WmUser wmUser = wmUserService.getById(userId);
        //1.2.2 通过自媒体用户得到作者ID
        articleDto.setAuthorId(wmUser.getApAuthorId());
        articleDto.setAuthorName(wmUser.getName());

        //1.3 设置文章频道信息“
        WmChannel channel = wmChannelService.getById(wmNews.getChannelId());
        if (channel != null) {
            articleDto.setChannelId(channel.getId());
            articleDto.setChannelName(channel.getName());
        }

        //1.4 设置布局
        articleDto.setLayout(wmNews.getType());

        //1.5 文章标记（0普通文章，1热点文章，2置顶文章。。。）
        articleDto.setFlag(MediaEnum.ARTICLE_FLAG_NORMAL.getVal());


        //2.远程调用文章微服务 发布文章（保存或者更新）
        ResponseResult<Long> result = articleFeign.saveArticle(articleDto);
        if (result.getCode().equals(AppHttpCodeEnum.SUCCESS.getCode())) {
            Long articleId = result.getData();
            //3. 更新自媒体文章中逻辑外键：正式发布后文章ID
            wmNews.setArticleId(articleId);
            wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_PUBLISH.getVal());
            wmNewsService.updateById(wmNews);
        }
    }


    /**
     * 从正文中获取图片 文本
     *
     * @param content
     * @return
     */
    private Map<String, Object> getTextAndImageFromContent(String content) {
        //0.定义文本结果
        StringBuilder stringBuilder = new StringBuilder();
        //0.1 定义图片集合
        List<String> images = new ArrayList<>();
        Map<String, Object> mapResult = new HashMap<>();
        //1.将内容转为JSON数组
        List<ContentDto> contentDtos = JSON.parseArray(content, ContentDto.class);
        //2.遍历数组对象 获取文本以及图片
        for (ContentDto dto : contentDtos) {
            if ("text".equals(dto.getType())) {
                stringBuilder.append(dto.getValue());
            } else {
                images.add(dto.getValue());
            }
        }
        mapResult.put("text", stringBuilder.toString());
        mapResult.put("images", images);
        return mapResult;
    }


    /**
     * 阿里云图片审核:审核自媒体文章对象中：封面，正文中图片,更新自媒体文章状态
     *
     * @param images 封面+正文中图片
     * @param wmNews 自媒体文章对象
     * @return
     */
    private boolean checkImage(List<String> images, WmNews wmNews) {
        boolean result = false;
        // 如果没有图片,不需要进行图片审核,直接返回true
        if (images.size() <= 0) {
            return true;
        }
        try {
            // 调用阿里云图片审核
            // Map map = imageScan.checkUrl(images);    // 如果minio部署在外网服务器,可以直接通过URL地址来审核
            // 先将图片从minio服务器下载下来,然后调用阿里云进行审核
            List<byte[]> imageList = new ArrayList<>();
            for (String url : images) {
                // 下载图片 TODO:下载图片来源地址 图片都是从MinIO来的
                InputStream inputStream = minIOService.download(url);
                byte[] bytes = IOUtils.toByteArray(inputStream);
                imageList.add(bytes);
            }
            Map map = aliyunGreenService.imageScan(imageList);
            String suggestion = (String) map.get("suggestion");
            // 判断结果
            switch (suggestion) {
                case "pass":
                    // 审核通过
                    result = true;
                    break;
                case "block":
                    // 审核不通过
                    wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_FAIL.getVal());
                    // 获取不通过的原因
                    String label = (String) map.get("label");
                    wmNews.setReason("阿里云图片审核不通过: " + label);
                    wmNewsService.updateById(wmNews);
                    break;
                case "review":
                    // 人工审核
                    wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_ARTIFICIAL.getVal());
                    wmNewsService.updateById(wmNews);
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("审核图片失败");
        }
        return result;
    }

    /**
     * 阿里云文本审核，更新自媒体文章状态
     *
     * @param text   文章文本
     * @param wmNews 自媒体文章对象
     * @return
     */
    private boolean checkText(String text, WmNews wmNews) {
        boolean result = false;
        try {
            Map map = aliyunGreenService.greenTextScan(text);
            String suggestion = (String) map.get("suggestion");
            // 判断结果
            switch (suggestion) {
                case "pass":
                    // 审核通过
                    result = true;
                    break;
                case "block":
                    // 审核不通过
                    wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_FAIL.getVal());
                    // 获取不通过的原因
                    String label = (String) map.get("label");
                    wmNews.setReason("阿里云文本审核不通过: " + label);
                    wmNewsService.updateById(wmNews);
                    break;
                case "review":
                    // 人工审核
                    wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_ARTIFICIAL.getVal());
                    wmNewsService.updateById(wmNews);
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("审核内容失败");
        }
        return result;
    }


    @Autowired
    private IWmSensitiveService sensitiveService;


    /**
     * 检查传入的文本是否包含敏感词
     *
     * @param text   文本
     * @param wmNews 自媒体用户发布的文章
     * @return
     */
    private boolean checkSensitive(String text, WmNews wmNews) {
        boolean result = false;
        //1.判断敏感词工具中的map是否为空，如果为空，从数据库中加载敏感词到Map中
        if (CollectionUtils.isEmpty(SensitiveWordUtil.dictionaryMap)) {
            LambdaQueryWrapper<WmSensitive> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(WmSensitive::getSensitives);

            //1.1 查询敏感词表中名称列
            List<WmSensitive> sensitiveList = sensitiveService.list(queryWrapper);
            if (!CollectionUtils.isEmpty(sensitiveList)) {
                List<String> collect = sensitiveList.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
                //1.2 初始化敏感词map
                SensitiveWordUtil.initMap(collect);
            }
        }
        //2. 审核标题
        Map<String, Integer> mapTitle = SensitiveWordUtil.matchWords(wmNews.getTitle());
        if (mapTitle.size() <= 0) {
            //2.1 文章标题审核通过
            //2.1.1 继续审核文章内容是否包含敏感词
            Map<String, Integer> mapContent = SensitiveWordUtil.matchWords(text);
            if (CollectionUtils.isEmpty(mapContent)) {
                //内容审核不包含敏感词
                result = true;
            } else {
                //2.1.3 内容审核失败
                wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_FAIL.getVal());
                Set<String> keySet = mapContent.keySet();
                String join = String.join(",", keySet);
                wmNews.setReason("内容中包含敏感词：" + join);
                wmNewsService.updateById(wmNews);
            }
        } else {
            //2.2 文章标题审核失败
            wmNews.setStatus(MediaEnum.NEWS_SUBMIT_STATUS_FAIL.getVal());
            Set<String> keySet = mapTitle.keySet();
            String join = String.join(",", keySet);
            wmNews.setReason("标题中包含敏感词：" + join);
            wmNewsService.updateById(wmNews);
            return false;
        }
        return result;
    }
}
